<?php
defined('BASEPATH') or exit('No direct script access allowed');

class Proc extends CI_Controller
{

	protected $flag = '';
	protected $pids = [];
	protected $process_num = 5;
	protected $is_set_signg_handler = false;


	function __construct()
	{
		pcntl_async_signals(true);
		parent::__construct();
	}	

	function check_is_master()
	{
		$pid = file_get_contents('./pid1');
		return getmypid() == $pid;
	}

	function set_system_signal_handler()
	{

		//平滑退出
		pcntl_signal(SIGTERM, function () {
			if ($this->check_is_master()) {
				//结束所有子进程
				foreach ($this->pids as $pid) {
					posix_kill($pid, SIGTERM);
				}
			}

			$this->flag = "exit";
		});

		// pcntl_signal(SIGCHLD,SIG_IGN);
		pcntl_signal(SIGCHLD, function () {
			//https://blog.csdn.net/weixin_33810006/article/details/85852972
			while (($pid = pcntl_waitpid(-1, $state, WNOHANG)) > 0) {
				writeln("$pid SIGCHLD");
				unset($this->pids[$pid]);
			}

			// $pid = pcntl_wait($status);

		});		
	}


	function get_pid_from_file()
	{
		$file = FCPATH . '/pid1';
		if (!file_exists($file)) throw new \Exception('pid 文件不存在', 10000);
		if (!is_readable($file)) throw new \Exception('pid 文件不可读');

		return file_get_contents($file);
	}

	function set_pid_file()
	{
		$pid = posix_getpid();
		$file = FCPATH . '/pid1';
		// if (!is_writable($file)) throw new \Exception('pid文件不可以写');
		file_put_contents($file, $pid);
	}

	function check_is_running()
	{
		try {
			$pid = $this->get_pid_from_file();
		} catch (\Throwable $ex) {
			//文件不存在
			if ($ex->getCode() != 10000) {
				throw $ex;
			} else {
				return;
			}
		}

		if (posix_kill($pid, 0)) {
			echo 'another process is running.........';
			exit();
		}
	}

	function index()
	{
		$args = func_get_args() ?? [''];
		$command = $args[0] ?? '';
		cli_set_process_title('yibai_jobs:master process');

		switch ($command) {
			case 'job:start':
				$this->job_start();
				break;
			case 'job:stop':
				$this->job_stop();
				break;
			default:
				writeln("usage:job:start | jobs:stop | job:reload");
				exit();
				break;
		}
	}

	function job_start()
	{
		// pcntl_exec("ps -ef|grep yibai|grep -v grep |awk '{print $2}'|xargs kill -9");
		$this->check_is_running();

		$pid = pcntl_fork();
		if ($pid < 0) throw new \Exception('deamon fork failure!');

		if ($pid > 0) {
			$this->set_system_signal_handler();
			//父进程直接退出,子进程由1号进程接管
			exit(0);
		} else {
			/*
            主要目的脱离终端控制，自立门户。
            创建一个新的会话，而且让这个pid统治这个会话，他既是会话组长，也是进程组长。
            而且谁也没法控制这个会话，除了这个pid。当然关机除外。。
            这时可以成做pid为这个无终端的会话组长。
            注意这个函数需要当前进程不是父进程，或者说不是会话组长。
            在这里当然不是，因为父进程已经被kill
            */
			$sid = posix_setsid();
			if ($sid < 0) {
				throw new \Exception('setsid error');
				exit;
			}

			chdir('/');

			writeln('master process:' . getmypid());
			$this->set_pid_file();
			file_put_contents('./sub_pids', '');
			cli_set_process_title('yibai_jobs:master process');
			$this->set_system_signal_handler();
			$this->create_workers($this->process_num);

			writeln('start ok:' . posix_getpid());
			writeln(json_encode($this->pids));

			while (true) {
				if ($this->flag == "exit" && empty($this->pids)) {
					writeln('all works are killed,master exit...');
					exit();
				}
				sleep(1);
				// writeln(json_encode($this->pids));
			}
		}
	}

	function job_stop()
	{
		$pid = $this->get_pid_from_file();
		if ($pid) posix_kill($pid, SIGTERM);
	}


	function create_workers($worker_num)
	{
		for ($i = 0; $i < $this->process_num; $i++) {
			$pid = pcntl_fork();
			if ($pid < 0) echo __METHOD__ . ' failure';

			if ($pid == 0) {
				$this->init_worker();
			} else {
				$this->pids[$pid] = $pid;
			}
		}
	}

	function init_worker()
	{
		cli_set_process_title('yibai_jobs:worker process');
		writeln('i am worker process:' . getmypid());
		$this->worker_process();
	}

	/**
	 * /cron/my_test/process  
	 */
	function worker_process()
	{
		file_put_contents('./sub_pids', getmypid() . ',', FILE_APPEND);

		pcntl_signal(SIGTERM, function () {
			writeln("work:" . getmypid() . " ready to exit!");
			$this->flag = "exit";
		});

		while (true) {
			try {
				// $ch = curl_init('http://127.0.0.1:9001/');
				// curl_exec($ch);
				// curl_close($ch);
				//业务逻辑
				// echo 1 / 0;
				// throw new \Exception('customer exception');
			} catch (\Throwable $ex) {
				echo sprintf('%s %s发生异常： %s %s', PHP_EOL, getmypid(), $ex->getMessage(), PHP_EOL);
			}

			if ($this->flag == 'exit') {
				// writeln('worker:' . getmypid() . ' ready to exit');
				// sleep(1);
				exit();
			}
			sleep(1);
		}
	}
}
